/*
 * Written by Guy Roberts of Object Skills Ltd, drifting
 * in cyberspace without a domain or email address right 
 * now. 
 * May 7 1993. You can use this as a base for a better application
 * as long as it is made publically available.
 */
 
/* Abandon hop all who enter here */

#import "Translator.h"

#define CLASSNAMEMARKER "@interface"
#define ENDOFCLASSMARKER "@end"
#define COPYRIGHTNOTICE "Release x.x  Copyright 1993\n\n\n" 
#define	TITLEFONTSIZE		28
#define TEXTFONTSIZE		14
#define	SUBTITLEFONTSIZE	18
#define	ONEINCHINTWIPS		1440
#define	INITIALCOUNT		0 /* Number elements in storage object */
#define BUFSIZE				1024 /* A temporary char buffer size */

#define TRANSLATOR_DEBUG1 1

@implementation Translator

- initForHeaderFile: (char *) theName withTable: table
{	
	int		length, maxLength;
	char	*streamBuffer;
	
	name = NXCopyStringBuffer(theName);
	numInstanceMethods = numClassMethods = 0;
	
	rtfInstance = [[RTF	alloc] init];

	if ((fileStream = NXMapFile(name, NX_READONLY)) == NULL)	{
        NXRunAlertPanel(NULL, [table	valueForStringKey: "fileNotFound"]
			, NULL, NULL, NULL, name);
		/* Should order out the window, and free everything, ie. close */
		return nil;
	}

	NXGetMemoryBuffer(fileStream, &streamBuffer, &length, &maxLength);

	if (!fileStream)
		return self;
	else {
		const char	*defaultDirPath = NXReadDefault(OWNER, "defaultDirectory");
		char		manPageName[BUFSIZE];
		char		*pathEnd;
		
		if (!defaultDirPath) { /* Use the same path as the header file */
			sprintf(manPageName, "%s", name);
			pathEnd = rindex(manPageName, '.');
			*pathEnd = 0;
			strcat(manPageName, ".rtf");
		}
		else { /* A default directory path is to be used */
			char	*fileName = rindex(name, '/');
			
			sprintf(manPageName, "%s%s", defaultDirPath, fileName);
			pathEnd = rindex(manPageName, '.');
			*pathEnd = 0;
			strcat(manPageName, ".rtf");
		}	
		/* Parse the header file */
		if (![self	parseBuffer: streamBuffer withKeyWordTable: table])
			return nil;
		
		/* Write the file out */
		NXSaveToFile([rtfInstance	stream], manPageName);
		[rtfInstance free]; /* This closes the stream */
	
		/* Only launch edit if the user chooses */
		{
			const char	*buffer = 
				NXReadDefault(OWNER, "automaticallyLaunchEdit");
	
			/* Launch the editor if the default value is not set or
			 * if it is set and has the value "YES" */
			if ( (!buffer) || ((buffer) && (strcmp(buffer, "YES") == 0)) )
				[[Application workspace]	
					openFile: manPageName withApplication: "Edit"];
		}
		
		NXCloseMemory(fileStream, NX_FREEBUFFER);
	}
	
	/* Free all */
	[self	free];
	
	return self;
}

/* This looks a mess. It is a mess. Messages are sent to the RTF instance
 * to ask for tabulation, newlines and different font treatments. This
 * code was finished off by my dog while I was asleep. Critiques should
 * be addressed to rover@objskills.demon.co.uk.
 */
- (BOOL) parseBuffer: (char *) buffer withKeyWordTable: table
{
	id		instanceMethodHT;
	id		classMethodHT;
	char	*className;
	char	*parentClassName;
	const char	*toBeWritten = [table	valueForStringKey: "toBeWritten"];
	
	/* Get the class name */
	if (![self getClass:  &className andParentName:  &parentClassName
	   		fromBuffer: &buffer]) {
		NXRunAlertPanel(NULL, [table	valueForStringKey: "invalidHeader"], 
				NULL, NULL, NULL, name);
		/* Should order out the window, and free everything, ie. close */
		return NO;
	}
	
	/* Set up the page */
	[rtfInstance	changeToHelvetica];
	[rtfInstance	append: COPYRIGHTNOTICE];
	[[rtfInstance	setFontSize: TITLEFONTSIZE] bold: YES];
	[rtfInstance	setLeftMargin: ONEINCHINTWIPS];
	/* Add the class name */
	[rtfInstance	changeToHelvetica];
	[[[rtfInstance	 tab] append: className] append: "\n\n"];
	[rtfInstance	changeToHelvetica];
	[rtfInstance	setFontSize: TEXTFONTSIZE];
	[[rtfInstance	tab]	
			append: [table	valueForStringKey: "inheritsFrom"]];
	[rtfInstance	changeToTimes];
	[[[[[rtfInstance	tab]  bold: NO]	
			append:	parentClassName]	append: "\n\n"] bold: YES];
	[rtfInstance	changeToHelvetica];
	[[rtfInstance	tab]	
			append: [table	valueForStringKey: "declaredIn"]];
	[rtfInstance			append: "\n\n\n"];
	[rtfInstance	setFontSize: SUBTITLEFONTSIZE];
	[rtfInstance	append: [table	valueForStringKey: "classDescription"]];
	[self	insertOrdinaryText: [table	valueForStringKey: "toBeWritten"]];
	[rtfInstance	append: [table	valueForStringKey: "instanceVariables"]];
	
	/* This method writes out a list of formatted instance variables */
	[self	listInstanceVariables: &buffer];
	[rtfInstance	append: [table	valueForStringKey: "methodTypes"]];
	
	/* Gather up all of the strings for class methods into a storage
	 * object and do the same for instance methods. The strings are 
	 * stored because they are used twice when composing the manual
	 * page.
	 */
	[self	getInstanceMethods: &instanceMethodHT 
				andClassMethods: &classMethodHT
				fromBuffer:  &buffer];

	/* Using the information in the storage objects, write out the 
	 * message names without the types or arguments.
	 */
	 
	[rtfInstance	setFontSize: TEXTFONTSIZE];
	[self	writeMessageNames: classMethodHT];
	[self	writeMessageNames: instanceMethodHT];
	[rtfInstance	append: "\n\n\n"];
	[rtfInstance	setFontSize: SUBTITLEFONTSIZE];
 
	[rtfInstance	append: [table	valueForStringKey: "classMethods"]];
	[self	writeMethods: classMethodHT	stringTable: table];
	[rtfInstance	append: [table	valueForStringKey: "instanceMethods"]];
	[self	writeMethods: instanceMethodHT	stringTable: table];

	[rtfInstance	append: 
						[table	valueForStringKey: "constantsAndDefinedTypes"]];
	[rtfInstance	append: "\n"];
	[self	insertOrdinaryText: [table	valueForStringKey: "toBeWritten"]];

	{
		/* Free all of the strings in the hash tables */
		int		i, j;
		
		for (i = 0; i < [classMethodHT count]; i++)
			NX_FREE([classMethodHT valueForKey: (const void *) i]);
			
		for (j = 0; j < [instanceMethodHT count]; j++)
			NX_FREE([instanceMethodHT valueForKey: (const void *) j]);
			
		/* Dispose of the tables */
		[classMethodHT	free];
		[instanceMethodHT		free];
	}
	
	return YES;
}

/* Put all of the class methods defined in the header file into a
 * table of strings. The keys to the table are of no meaning. In
 * fact I should have used the Storage class to save the strings.
 */
- (BOOL) getInstanceMethods: (id *) instanceMethodHT 
		andClassMethods: (id *) classMethodHT
		fromBuffer: (char **) buffer
{
	char	*start = NULL;
	char	*end = NULL;
	char	*tmpBufferOne = NXCopyStringBuffer(*buffer);
	char	*tmpBufferTwo = NXCopyStringBuffer(*buffer);
	
	*instanceMethodHT = [[HashTable	alloc] 
								initKeyDesc: "i" valueDesc:"*"];
	*classMethodHT	 = [[HashTable	alloc]
								initKeyDesc: "i" valueDesc:"*"];
	
	/* Collect the class method definitions */
	start = tmpBufferOne;
	while (start = index(start, '+') ) {
		char	*newString = NULL;
		
		/* Create a copy of the method definition string */
		end = index(start, ';');
		*end = '\0';
		newString = NXCopyStringBuffer(start);
		[*classMethodHT	insertKey: (const void *) numClassMethods++ 
				value: newString];
		
		start = end + 1; /* Hop over the null character */
	}

	/* Collect the instance method definitions */
	start = tmpBufferTwo;
	while (start = index(start, '-') ) {
		char	*newString = NULL;
		
		/* Create a copy of the method definition string */
		end = index(start, ';');
		*end = '\0';
		newString = NXCopyStringBuffer(start);
		[*instanceMethodHT	insertKey: (const void *) numInstanceMethods++ 
				value: newString];

		start = end + 1; /* Hop over the null character */
	}
	
	/* Tidy up */
	NX_FREE(tmpBufferOne);
	NX_FREE(tmpBufferOne);
	
	return NO;
}

/* This method is used to go through a table of strings and
 * break down each one into the parts of a message name. They
 * need to be broken apart so as to be able to make bits of
 * string bold or italic.
 */
- writeMethods: table stringTable: stringTable
{
	int		i;
	char	*namePart = NULL;
	char	*typePart = NULL;
	char	*argPart = NULL;
	
	[rtfInstance	setFontSize: TEXTFONTSIZE];
	
	for (i = 0; i < [table count]; i++) {
		char	buffer[BUFSIZE], *bufPtr;
		BOOL	isFirstNamePart = YES;

		memset(buffer, '\0', BUFSIZE);
		strcpy(buffer, [table valueForKey: (const void *) i]);
	
		bufPtr = buffer;

		while (bufPtr && (strcmp(bufPtr, "") != 0) ) {
			[self	getNamePart:  &namePart typePart: &typePart
				argPart:  &argPart	fromString:  &bufPtr];
		
			[rtfInstance	bold: YES];
			if (isFirstNamePart) {	
				char	tmpName[BUFSIZE];

				{
					/* Get rid of the '+' or '-' character in the name.
					 * You are deep inside Hack land here.
					 */
					char 	*ptr = NULL;
					
					tmpName[0] = '\0';
					strcat(tmpName, namePart);
					ptr = index(tmpName, '+');
					if (!ptr)
						ptr = index(tmpName, '-');
					if (ptr)
						*ptr = ' ';
				}
				[rtfInstance	changeToHelvetica];
				isFirstNamePart = NO;
				[[rtfInstance	append:"\n"] tab];
				[rtfInstance	append: tmpName];
				[[[rtfInstance	append:"\n"] tab] tab];
			}
			[rtfInstance	changeToTimes];
	
			[rtfInstance	append: namePart];
			[rtfInstance	bold: NO];
			[rtfInstance	append:" "];
			[rtfInstance	append: typePart];
			[rtfInstance	italic: YES];
			[rtfInstance	append: argPart];
			[rtfInstance	italic: NO];
			[[[[rtfInstance	append: "\n"] tab] tab] tab];
			
			/* Free the three strings */
			NX_FREE(namePart);
			NX_FREE(typePart);
			NX_FREE(argPart);
		}
		[[[rtfInstance	append:"\n"] tab]
				append: [stringTable	valueForStringKey: "toBeWritten"]];
		
	} /* for */	
	
	[[rtfInstance	setFontSize: SUBTITLEFONTSIZE] bold: YES];
	[rtfInstance	append:"\n\n"];
	
	return self;
}

/* 
 * This method is used to write out a summary of every message
 * name in a hash table. This goes near the top of the manual 
 * page. It is called twice, once with a hash table of instance
 * method strings and once with the class methods.
 */
- writeMessageNames: table
{
	int		i;
	char	*namePart = NULL;
	char	*typePart = NULL;
	char	*argPart = NULL;
	
	[rtfInstance	changeToTimes];

	for (i = 0; i < [table count]; i++) {
		char	buffer[BUFSIZE], *bufPtr;
		
		memset(buffer, '\0', BUFSIZE);
		strcpy(buffer, [table valueForKey: (const void *) i]);
	
		bufPtr = buffer;
		[[[rtfInstance	append: "\n"] tab]	bold: NO];

		while (bufPtr && (strcmp(bufPtr, "") != 0) ) {
			[self	getNamePart:  &namePart typePart: &typePart
				argPart:  &argPart	fromString:  &bufPtr];
		
			[rtfInstance	append: namePart];
			[rtfInstance	append: ""];
			
			/* Free the three strings */
			if (namePart)
				NX_FREE(namePart);
			if (typePart)
				NX_FREE(typePart);
			if (argPart)
				NX_FREE(argPart);
		}

	} /* for */	
	
	[rtfInstance	changeToHelvetica];
	[rtfInstance	bold: YES];
	
	return self;
}

- listInstanceVariables: (char **) buffer
{
	char	*pos = index(*buffer, '{');
	char	type[BUFSIZE];
	char	nom[BUFSIZE];
	
	[rtfInstance	setFontSize: TEXTFONTSIZE];
	[rtfInstance	append: "\n"];
	[rtfInstance	changeToTimes];

	while ([self getNextType: type andVariableName: nom fromBuffer: buffer]) {
		[rtfInstance	tab];
		[rtfInstance	bold: NO];
		[rtfInstance	append: type];
		[rtfInstance	tab];
		[rtfInstance	bold: YES];
		[rtfInstance	append: nom];
		[rtfInstance	append: "\n"];
	}
	
	[rtfInstance	setFontSize: SUBTITLEFONTSIZE];
	[rtfInstance	append: "\n"];

	[rtfInstance	changeToHelvetica];

	return self;
}

/* 
 * Used to collect the instance variable parts.
 */
- (BOOL) getNextType: (char *) type andVariableName: (char *) nom 
	fromBuffer: (char **) buffer
{
	int		i = 0, j = 0;
	
	if (**buffer == '{')
		(*buffer)++;
		
	/* Eat white space */
	while ( (**buffer != '}') && (isspace(**buffer)) )
		(*buffer)++;
	if (**buffer == '}') {
		(*buffer)++;
		return NO;
	}
	/* Collect the type of the instance variable */
	while (!isspace(**buffer) ) {
		type[i++] = **buffer;
		(*buffer)++;
	}
	type[i] = '\0';
	
	/* Eat white space */
	while ( (**buffer != '}') && (isspace(**buffer)) )
		(*buffer)++;
	if (**buffer == '}') {
		(*buffer)++;
		return NO;
	}
	/* Collect the name of the variable */
	while ((**buffer) != '\n') {
		nom[j++] = **buffer;
		(*buffer)++;
	}
	nom[j] =  '\0';
	
	return YES;
}

/*
 * I never claimed to be an OO expert and stuff like this
 * shows it.
 */
- insertOrdinaryText: (const char *) text
{
	[rtfInstance	changeToTimes];
	[rtfInstance	setFontSize: TEXTFONTSIZE];
	[rtfInstance	bold: NO];
	[rtfInstance	tab];
	[rtfInstance	append: text];	
	[rtfInstance	bold: YES];
	[rtfInstance	setFontSize: SUBTITLEFONTSIZE];
	[rtfInstance	changeToHelvetica];

	return self;
}

- free
{
	if (name)
		NX_FREE(name);
#if 0
	return [super free];
#endif
	return nil;
}

/*
 * Recover the class name and the parent class name.
 */
- (BOOL) getClass: (char **) className 
	   andParentName: (char **) parentClassName
	   fromBuffer: (char **) buf
{
	char	*endOfClassName = NULL;
	char	*endOfParentClassName = NULL;
	char	*buffer = *buf;
	
	*className = strstr(buffer, CLASSNAMEMARKER);
	if (!(*className))
		return NO; /* Not an Objective-C header file */
	*className += strlen(CLASSNAMEMARKER);
	while (!isalpha(**className))
		(*className)++;

	*parentClassName = NULL;
	endOfClassName = index(*className, ':');
	*endOfClassName = '\0';
	*parentClassName = endOfClassName + 1;
	endOfParentClassName = index(*parentClassName, '\n');
	*endOfParentClassName = '\0';
	
	/* Move through the buffer */
	*buf = endOfParentClassName + 1;
	
	if (!parentClassName)
		return NO;
	else
		return YES;
}

/* This method can be called repeatedly with a string containing a
 * method definition. It returns the components of that string. These
 * parts can then be written onto the manual page in bold or italic
 * text as needed.
 * It is assumed that the string passed in is of the correct format.
 * If the parsing fails, NO is returned.
 * Do not forget to eventually free the strings in the calling method.
 * Disclaimer: I am not proud of this parsing code. I promise to learn
 * about lex in the future.
 */
- (BOOL) getNamePart: (char **) namePart typePart: (char **) typePart
	argPart: (char **) argPart
	fromString: (char **) string
{
	char	buffer[BUFSIZE], *marker;
	BOOL	bracedType = NO;
	int		j = 0;
	
#ifdef TRANSLATOR_DEBUG
	printf("%s\n", *string);
#endif

	marker = *string;
	
	/* Move past white space */
	while (isspace(*marker))
		marker++;

	/* The name part is up to the first colon */
	while ((*marker) && (*marker != ':') ) {
		buffer[j++] = *marker++;
	}
	if (*marker == ':')
		buffer[j++] = *marker++; /* Include the colon */
	buffer[j] = '\0';
	*namePart = NXCopyStringBuffer(buffer);

	if ( (!marker) || (strcmp(marker, "") == 0) ) {
		*typePart = NXCopyStringBuffer("");
		*argPart = NXCopyStringBuffer("");
		*string = marker; /* move on to the next part of the line */
		return YES;
	}
	
	/* The type part is next, a type expression may be within braces */
	j = 0;
	/* Move past white space */
	while (isspace(*marker))
		marker++;
	
	if (*marker == '(')
		bracedType = YES;
		
	if (!bracedType) { /* No braces used */
		while ((*marker) && !isspace(*marker))
			buffer[j++] = *marker++;
	}
	else { /* White space is part of the type expression */
		while (*marker != ')')
			buffer[j++] = *marker++;
		buffer[j++] = *marker++; /* Include the closing brace */
	}
	buffer[j] = '\0';
	*typePart = NXCopyStringBuffer(buffer);

	/* Move past white space */
	while (isspace(*marker))
		marker++;
		
	/* The argument part is next, it runs up to the next space or new line */
	j = 0;
	while (*marker && !isspace(*marker) && (*marker != ';') )
		buffer[j++] = *marker++;

	buffer[j] = '\0';
	*argPart = NXCopyStringBuffer(buffer);

	/* Gulp, now a confusing bit. The above code assumes that method 
	 * definitions are made up of blocks of three parts, a name, a
	 * type and a variable name. Of course if the type is (id) it can
	 * be ommitted and the code given above fails. eg. "myMethod: anObj;"
	 * So if the variable name has a colon in it or is a null string,
	 * make the type string null and the variable name equal to the type
	 * string.
	 */
	*string = marker;
	if (index(*argPart, ':') || (strcmp(*argPart, "") == 0) ) {
		char	*tmp;
		
		/* Must rewind the string pointer !! Arrrg */
		if (tmp = index(*argPart, ':')) {
			int	lengthToRewind = strlen(*argPart);
			
			*string = *string - lengthToRewind;
		}

		*argPart = NXCopyStringBuffer(*typePart);
		*typePart = NXCopyStringBuffer("");		
	}
		
	return YES;
}

@end
